#include <iostream>
#include <chrono>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <atomic>
#include <random>

using namespace std;

typedef struct node_s {
    struct node_s *next;
    int n;
} node_t;

typedef struct data_s {
    atomic<bool> shutdown;

    node_t *volatile head;
    node_t *volatile near;

    mutex mtx;
    condition_variable cv;

    data_s() {
        this->shutdown = false;
        this->head = this->near = new node_t;
    }

    ~data_s() {
        while (this->head) {
            this->near = this->head->next;
            delete this->head;

            this->head = this->near;
        }
    }
} data_t;


static default_random_engine e;
static uniform_int_distribution<unsigned> u_consume(0, 5);
static uniform_int_distribution<unsigned> u_produce(0, 2);


void consume(data_t *data) {
    unique_lock<mutex> lck(data->mtx);

    int total = 0;

    for(;;) {
        if (data->head == data->near) {
            if (data->shutdown) break;
            data->cv.wait_for(lck, chrono::seconds(1));
        } else {
            node_t *p = data->head;
            total += 1;
            cout << "[ consumer ] n: " << p->n << ", total: " << total << endl;
            //this_thread::yield();
            this_thread::sleep_for(chrono::milliseconds(u_consume(e)));
            data->head = p->next;
            delete p;
        }
    }
}

void produce(data_t *data) {
    int i = 1;

    for(;;) {
        node_t *p = new node_t;

        data->near->next = p;
        data->near->n = i;
        data->near = p;

        cout << "[ produce ] n: " << i << endl;
        data->cv.notify_one();

        if ((++i) > 1000) {
            data->shutdown = true;
            break;
        } else {
            //this_thread::yield();
            this_thread::sleep_for(chrono::milliseconds(u_produce(e)));
        }
    }
}

int main() {
    data_t data;


    cout << "[ main ] begin... " << endl;

    thread consumer(consume, &data);
    thread producer(produce, &data);

    cout << "[ main ] begin sleeping... " << endl;

    while (!data.shutdown)
        this_thread::sleep_for(chrono::seconds(1));

    cout << "[ main ] finished sleeping, begin shutdown... " << endl;
    producer.join();
    consumer.join();
    cout << "[ main ] shutdown." << endl;

    return 0;
}
